Skip to content

Ongoing call tiles - Implement the view-models and store#34198

Open
MidhunSureshR wants to merge 20 commits into
midhun/call/ongoing-tiles/viewsfrom
midhun/call/ongoing-tiles/vms
Open

Ongoing call tiles - Implement the view-models and store#34198
MidhunSureshR wants to merge 20 commits into
midhun/call/ongoing-tiles/viewsfrom
midhun/call/ongoing-tiles/vms

Conversation

@MidhunSureshR

@MidhunSureshR MidhunSureshR commented Jul 8, 2026

Copy link
Copy Markdown
Member

Branched from #34197

This PR implements the view-models necessary for driving the views. There's no foolproof way to associate a given call to a rtc notification event (which is what we render as tiles) so the basic idea is to treat the last notification tile in the timeline as an ongoing call tile if there's a call in the room.

  • There's a BaseOngoingCallTileViewModel that provides the shared snapshot content. DmOngoingCallTileViewModel and RoomOngoingCallTileViewModel both extend this base view model to provide the part of the snapshot that is not common.
  • Implements FacePileViewModel, MemberAvatarViewModel and DurationViewModel for driving the views from the last PR.
  • There's a new store, LatestRtcNotificationEventStore, that tracks the latest notification event in a given room. This is necessary because the call store tells you about a new call as soon as a call membership event comes through but the timeline only renders rtc notification events. The problem arises when there's a delay between receiving the call membership event and the notification event. As a result, CallStore tells us that a call is ongoing in the room but the corresponding rtc notification event hasn't arrived, so we incorrectly show the last call tile (which in reality relates to a call that is already over) as ongoing.

@MidhunSureshR MidhunSureshR changed the base branch from develop to midhun/call/ongoing-tiles/views July 8, 2026 21:04
@MidhunSureshR MidhunSureshR marked this pull request as ready for review July 8, 2026 21:10
@MidhunSureshR MidhunSureshR requested review from a team as code owners July 8, 2026 21:10
@MidhunSureshR MidhunSureshR requested review from AndrewFerr, Half-Shot and dbkr and removed request for a team July 8, 2026 21:10

@dbkr dbkr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phew, this is quite a chunky one (I wonder if we should have landed these tiles one by one or similar).

Comment thread apps/web/src/components/viewmodels/avatars/MemberAvatarViewModel.test.ts Outdated
cli: MatrixClient;
}

function computeSnapshot(props: Props): MemberAvatarViewSnapshot {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels a bit weird that computeSnapshot is sometimes a private member function (FacePileViewModel) and sometimes an independent function of the props. I wonder if we should be consistent.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer it to always be a private member function but sometimes you need to calculate the initial snapshot before you can call any methods of the vm (because you need to pass it to super()). So far we've treated this as an implementation detail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right. I wonder if we could make it always a separate function. Not something for now though.

Comment thread apps/web/src/components/viewmodels/avatars/MemberAvatarViewModel.ts Outdated
Comment thread apps/web/src/stores/LatestRtcNotificationEventStore.ts Outdated
Comment thread apps/web/src/stores/LatestRtcNotificationEventStore.ts Outdated
T extends CommonOngoingCallTileViewSnapshot = CommonOngoingCallTileViewSnapshot,
> extends BaseViewModel<T, Props> {
public constructor(props: Props, extraSnapshot: Partial<T> = {}) {
const snapshot = { ...computeSnapshot(props), ...extraSnapshot };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

computeSnapshot throws a few exceptions: are they handled by whatever constructs this and can we document this as throwing them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These errors are unlikely to be thrown:

  1. The !roomId throw is unlikely to happen because MatrixEvent.getRoomId() only returns undefined for presence events.
  2. The !startedUserId throw is unlikely to happen because all events have a sender and the undefined type is an implementation quirk from js-sdk.
  3. The !room throw is unlikely to happen because you wouldn't end up in this code without the room existing.
  4. The !startedMember throw is unlikely to happen because MatrixEvent.sender is only undefined for presence events.

But we do have some designs for an error tile so happy to do a follow up PR that collects the errors and renders them out in the timeline.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they're unlikely then it's probably fine if they're caught by the event tile error boundary

const call = getCallInRoom(this.props.callStore, this.props.roomId);
this.disposables.trackListener(call, CallEvent.Participants, ((participants: Map<RoomMember, Set<string>>) => {
this.onParticipantsChange(participants);
}) as (...args: unknown[]) => void);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The casts here (and above) seem a bit weird

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is but this is a typing issue in the base view model code. Will fix separately.

Comment on lines +21 to +29
function isCallDeclinedByOwnUser(
notificationEvent: MatrixEvent,
getRelationsForEvent: GetRelationsForEvent | undefined,
cli: MatrixClient,
): boolean {
const declinedEvents = getDeclinedEvents(notificationEvent, getRelationsForEvent);
const ownUserId = cli.getUserId();
return declinedEvents?.some((event) => event.getSender() === ownUserId) ?? false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sort of surprised tools like this aren't available in the call code itself: it feels quite generally useful, but I guess it can always be moved if needed elsewhere.

@dbkr dbkr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, looks sensible I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants